<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or http://www.getid3.org                         //
//                                                             //
//  FLV module by Seth Kaufman <seth@whirl-i-gig.com>          //
//                                                             //
//  * version 0.1 (26 June 2005)                               //
//                                                             //
//  minor modifications by James Heinrich <info@getid3.org>    //
//  * version 0.1.1 (15 July 2005)                             //
//                                                             //
//  Support for On2 VP6 codec and meta information             //
//    by Steve Webster <steve.webster@featurecreep.com>        //
//  * version 0.2 (22 February 2006)                           //
//                                                             //
//  Modified to not read entire file into memory               //
//    by James Heinrich <info@getid3.org>                      //
//  * version 0.3 (15 June 2006)                               //
//                                                             //
//  Bugfixes for incorrectly parsed FLV dimensions             //
//    and incorrect parsing of onMetaTag                       //
//    by Evgeny Moysevich <moysevich@gmail.com>                //
//  * version 0.4 (07 December 2007)                           //
//                                                             //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.flv.php                                  //
// module for analyzing Shockwave Flash Video files            //
// dependencies: NONE                                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

define('GETID3_FLV_TAG_AUDIO', 8);
define('GETID3_FLV_TAG_VIDEO', 9);
define('GETID3_FLV_TAG_META', 18);

define('GETID3_FLV_VIDEO_H263',   2);
define('GETID3_FLV_VIDEO_SCREEN', 3);
define('GETID3_FLV_VIDEO_VP6',    4);



class getid3_flv
{

	function getid3_flv(&$fd, &$ThisFileInfo, $ReturnAllTagData=false) {
//$start_time = microtime(true);
		fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);

		$FLVdataLength = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'];
		$FLVheader = fread($fd, 5);

		$ThisFileInfo['fileformat'] = 'flv';
		$ThisFileInfo['flv']['header']['signature'] =                           substr($FLVheader, 0, 3);
		$ThisFileInfo['flv']['header']['version']   = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
		$TypeFlags                                  = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));

		if ($ThisFileInfo['flv']['header']['signature'] != 'FLV') {
			$ThisFileInfo['error'][] = 'Expecting "FLV" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['flv']['header']['signature'].'"';
			unset($ThisFileInfo['flv']);
			unset($ThisFileInfo['fileformat']);
			return false;
		}

		$ThisFileInfo['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
		$ThisFileInfo['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);

		$FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($fd, 4));
		$FLVheaderFrameLength = 9;
		if ($FrameSizeDataLength > $FLVheaderFrameLength) {
			fseek($fd, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
		}
//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>';

		$Duration = 0;
		$found_video = false;
		$found_audio = false;
		$found_meta  = false;
		while ((ftell($fd) + 16) < $ThisFileInfo['avdataend']) {
			$ThisTagHeader = fread($fd, 16);

			$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  0, 4));
			$TagType           = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  4, 1));
			$DataLength        = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  5, 3));
			$Timestamp         = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  8, 3));
			$LastHeaderByte    = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
			$NextOffset = ftell($fd) - 1 + $DataLength;
			if ($Timestamp > $Duration) {
				$Duration = $Timestamp;
			}

//echo __LINE__.'['.ftell($fd).']=('.$TagType.')='.number_format(microtime(true) - $start_time, 3).'<br>';

			switch ($TagType) {
				case GETID3_FLV_TAG_AUDIO:
					if (!$found_audio) {
						$found_audio = true;
						$ThisFileInfo['flv']['audio']['audioFormat']     =  $LastHeaderByte & 0x07;
						$ThisFileInfo['flv']['audio']['audioRate']       = ($LastHeaderByte & 0x30) / 0x10;
						$ThisFileInfo['flv']['audio']['audioSampleSize'] = ($LastHeaderByte & 0x40) / 0x40;
						$ThisFileInfo['flv']['audio']['audioType']       = ($LastHeaderByte & 0x80) / 0x80;
					}
					break;

				case GETID3_FLV_TAG_VIDEO:
					if (!$found_video) {
						$found_video = true;
						$ThisFileInfo['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;

						$FLVvideoHeader = fread($fd, 11);

						if ($ThisFileInfo['flv']['video']['videoCodec'] != GETID3_FLV_VIDEO_VP6) {

							$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
							$PictureSizeType = $PictureSizeType & 0x0007;
							$ThisFileInfo['flv']['header']['videoSizeType'] = $PictureSizeType;
							switch ($PictureSizeType) {
								case 0:
									//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
									//$PictureSizeEnc <<= 1;
									//$ThisFileInfo['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
									//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
									//$PictureSizeEnc <<= 1;
									//$ThisFileInfo['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;

									$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2));
									$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
									$PictureSizeEnc['x'] >>= 7;
									$PictureSizeEnc['y'] >>= 7;
									$ThisFileInfo['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
									$ThisFileInfo['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
									break;

								case 1:
									$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3));
									$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3));
									$PictureSizeEnc['x'] >>= 7;
									$PictureSizeEnc['y'] >>= 7;
									$ThisFileInfo['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
									$ThisFileInfo['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
									break;

								case 2:
									$ThisFileInfo['video']['resolution_x'] = 352;
									$ThisFileInfo['video']['resolution_y'] = 288;
									break;

								case 3:
									$ThisFileInfo['video']['resolution_x'] = 176;
									$ThisFileInfo['video']['resolution_y'] = 144;
									break;

								case 4:
									$ThisFileInfo['video']['resolution_x'] = 128;
									$ThisFileInfo['video']['resolution_y'] = 96;
									break;

								case 5:
									$ThisFileInfo['video']['resolution_x'] = 320;
									$ThisFileInfo['video']['resolution_y'] = 240;
									break;

								case 6:
									$ThisFileInfo['video']['resolution_x'] = 160;
									$ThisFileInfo['video']['resolution_y'] = 120;
									break;

								default:
									$ThisFileInfo['video']['resolution_x'] = 0;
									$ThisFileInfo['video']['resolution_y'] = 0;
									break;

							}
						}
					}
					break;

				// Meta tag
				case GETID3_FLV_TAG_META:
					if (!$found_meta) {
						$found_meta = true;
						fseek($fd, -1, SEEK_CUR);
						$reader = new AMFReader(new AMFStream(fread($fd, $DataLength)));
						$eventName = $reader->readData();
						$ThisFileInfo['meta'][$eventName] = $reader->readData();
						unset($reader);

						$ThisFileInfo['video']['frame_rate']   = @$ThisFileInfo['meta']['onMetaData']['framerate'];
						$ThisFileInfo['video']['resolution_x'] = @$ThisFileInfo['meta']['onMetaData']['width'];
						$ThisFileInfo['video']['resolution_y'] = @$ThisFileInfo['meta']['onMetaData']['height'];
					}
					break;

				default:
					// noop
					break;
			}

			fseek($fd, $NextOffset, SEEK_SET);
		}

		if ($ThisFileInfo['playtime_seconds'] = $Duration / 1000) {
		    $ThisFileInfo['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['playtime_seconds'];
		}

		if ($ThisFileInfo['flv']['header']['hasAudio']) {
			$ThisFileInfo['audio']['codec']           =   $this->FLVaudioFormat($ThisFileInfo['flv']['audio']['audioFormat']);
			$ThisFileInfo['audio']['sample_rate']     =     $this->FLVaudioRate($ThisFileInfo['flv']['audio']['audioRate']);
			$ThisFileInfo['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($ThisFileInfo['flv']['audio']['audioSampleSize']);

			$ThisFileInfo['audio']['channels']   = $ThisFileInfo['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
			$ThisFileInfo['audio']['lossless']   = ($ThisFileInfo['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
			$ThisFileInfo['audio']['dataformat'] = 'flv';
		}
		if (@$ThisFileInfo['flv']['header']['hasVideo']) {
			$ThisFileInfo['video']['codec']      = $this->FLVvideoCodec($ThisFileInfo['flv']['video']['videoCodec']);
			$ThisFileInfo['video']['dataformat'] = 'flv';
			$ThisFileInfo['video']['lossless']   = false;
		}

		return true;
	}


	function FLVaudioFormat($id) {
		$FLVaudioFormat = array(
			0 => 'uncompressed',
			1 => 'ADPCM',
			2 => 'mp3',
			5 => 'Nellymoser 8kHz mono',
			6 => 'Nellymoser',
		);
		return (@$FLVaudioFormat[$id] ? @$FLVaudioFormat[$id] : false);
	}

	function FLVaudioRate($id) {
		$FLVaudioRate = array(
			0 =>  5500,
			1 => 11025,
			2 => 22050,
			3 => 44100,
		);
		return (@$FLVaudioRate[$id] ? @$FLVaudioRate[$id] : false);
	}

	function FLVaudioBitDepth($id) {
		$FLVaudioBitDepth = array(
			0 =>  8,
			1 => 16,
		);
		return (@$FLVaudioBitDepth[$id] ? @$FLVaudioBitDepth[$id] : false);
	}

	function FLVvideoCodec($id) {
		$FLVvideoCodec = array(
			GETID3_FLV_VIDEO_H263   => 'Sorenson H.263',
			GETID3_FLV_VIDEO_SCREEN => 'Screen video',
			GETID3_FLV_VIDEO_VP6    => 'On2 VP6',
		);
		return (@$FLVvideoCodec[$id] ? @$FLVvideoCodec[$id] : false);
	}
}

class AMFStream {
	var $bytes;
	var $pos;

	function AMFStream(&$bytes) {
		$this->bytes =& $bytes;
		$this->pos = 0;
	}

	function readByte() {
		return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
	}

	function readInt() {
		return ($this->readByte() << 8) + $this->readByte();
	}

	function readLong() {
		return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
	}

	function readDouble() {
		return getid3_lib::BigEndian2Float($this->read(8));
	}

	function readUTF() {
		$length = $this->readInt();
		return $this->read($length);
	}

	function readLongUTF() {
		$length = $this->readLong();
		return $this->read($length);
	}

	function read($length) {
		$val = substr($this->bytes, $this->pos, $length);
		$this->pos += $length;
		return $val;
	}

	function peekByte() {
		$pos = $this->pos;
		$val = $this->readByte();
		$this->pos = $pos;
		return $val;
	}

	function peekInt() {
		$pos = $this->pos;
		$val = $this->readInt();
		$this->pos = $pos;
		return $val;
	}

	function peekLong() {
		$pos = $this->pos;
		$val = $this->readLong();
		$this->pos = $pos;
		return $val;
	}

	function peekDouble() {
		$pos = $this->pos;
		$val = $this->readDouble();
		$this->pos = $pos;
		return $val;
	}

	function peekUTF() {
		$pos = $this->pos;
		$val = $this->readUTF();
		$this->pos = $pos;
		return $val;
	}

	function peekLongUTF() {
		$pos = $this->pos;
		$val = $this->readLongUTF();
		$this->pos = $pos;
		return $val;
	}
}

class AMFReader {
	var $stream;

	function AMFReader(&$stream) {
		$this->stream =& $stream;
	}

	function readData() {
		$value = null;

		$type = $this->stream->readByte();
		switch ($type) {

			// Double
			case 0:
				$value = $this->readDouble();
			break;

			// Boolean
			case 1:
				$value = $this->readBoolean();
				break;

			// String
			case 2:
				$value = $this->readString();
				break;

			// Object
			case 3:
				$value = $this->readObject();
				break;

			// null
			case 6:
				return null;
				break;

			// Mixed array
			case 8:
				$value = $this->readMixedArray();
				break;

			// Array
			case 10:
				$value = $this->readArray();
				break;

			// Date
			case 11:
				$value = $this->readDate();
				break;

			// Long string
			case 13:
				$value = $this->readLongString();
				break;

			// XML (handled as string)
			case 15:
				$value = $this->readXML();
				break;

			// Typed object (handled as object)
			case 16:
				$value = $this->readTypedObject();
				break;

			// Long string
			default:
				$value = '(unknown or unsupported data type)';
			break;
		}

		return $value;
	}

	function readDouble() {
		return $this->stream->readDouble();
	}

	function readBoolean() {
		return $this->stream->readByte() == 1;
	}

	function readString() {
		return $this->stream->readUTF();
	}

	function readObject() {
		// Get highest numerical index - ignored
//		$highestIndex = $this->stream->readLong();

		$data = array();

		while ($key = $this->stream->readUTF()) {
			$data[$key] = $this->readData();
		}
		// Mixed array record ends with empty string (0x00 0x00) and 0x09
		if (($key == '') && ($this->stream->peekByte() == 0x09)) {
			// Consume byte
			$this->stream->readByte();
		}
		return $data;
	}

	function readMixedArray() {
		// Get highest numerical index - ignored
		$highestIndex = $this->stream->readLong();

		$data = array();

		while ($key = $this->stream->readUTF()) {
			if (is_numeric($key)) {
				$key = (float) $key;
			}
			$data[$key] = $this->readData();
		}
		// Mixed array record ends with empty string (0x00 0x00) and 0x09
		if (($key == '') && ($this->stream->peekByte() == 0x09)) {
			// Consume byte
			$this->stream->readByte();
		}

		return $data;
	}

	function readArray() {
		$length = $this->stream->readLong();
		$data = array();

		for ($i = 0; $i < $length; $i++) {
			$data[] = $this->readData();
		}
		return $data;
	}

	function readDate() {
		$timestamp = $this->stream->readDouble();
		$timezone = $this->stream->readInt();
		return $timestamp;
	}

	function readLongString() {
		return $this->stream->readLongUTF();
	}

	function readXML() {
		return $this->stream->readLongUTF();
	}

	function readTypedObject() {
		$className = $this->stream->readUTF();
		return $this->readObject();
	}
}


/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or http://www.getid3.org                         //
/////////////////////////////////////////////////////////////////
//                                                             //
// getid3.lib.php - part of getID3()                           //
// See readme.txt for more details                             //
//                                                            ///
/////////////////////////////////////////////////////////////////


class getid3_lib
{


	function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
		$intvalue = 0;
		$bytewordlen = strlen($byteword);
		for ($i = 0; $i < $bytewordlen; $i++) {
			if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
				$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7);
			} else {
				$intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i));
			}
		}
		if ($signed && !$synchsafe) {
			// synchsafe ints are not allowed to be signed
			switch ($bytewordlen) {
				case 1:
				case 2:
				case 3:
				case 4:
					$signmaskbit = 0x80 << (8 * ($bytewordlen - 1));
					if ($intvalue & $signmaskbit) {
						$intvalue = 0 - ($intvalue & ($signmaskbit - 1));
					}
					break;

				default:
					die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2Int()');
					break;
			}
		}
		return getid3_lib::CastAsInt($intvalue);
	}

	function CastAsInt($floatnum) {
		// convert to float if not already
		$floatnum = (float) $floatnum;

		// convert a float to type int, only if possible
		if (getid3_lib::trunc($floatnum) == $floatnum) {
			// it's not floating point
			if ($floatnum <= 2147483647) { // 2^31
				// it's within int range
				$floatnum = (int) $floatnum;
			}
		}
		return $floatnum;
	}

	function trunc($floatnumber) {
		// truncates a floating-point number at the decimal point
		// returns int (if possible, otherwise float)
		if ($floatnumber >= 1) {
			$truncatednumber = floor($floatnumber);
		} elseif ($floatnumber <= -1) {
			$truncatednumber = ceil($floatnumber);
		} else {
			$truncatednumber = 0;
		}
		if ($truncatednumber <= 1073741824) { // 2^30
			$truncatednumber = (int) $truncatednumber;
		}
		return $truncatednumber;
	}

	function BigEndian2Float($byteword) {
		// ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
		// http://www.psc.edu/general/software/packages/ieee/ieee.html
		// http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html

		$bitword = getid3_lib::BigEndian2Bin($byteword);
		if (!$bitword) {
            return 0;
        }
		$signbit = $bitword{0};

		switch (strlen($byteword) * 8) {
			case 32:
				$exponentbits = 8;
				$fractionbits = 23;
				break;

			case 64:
				$exponentbits = 11;
				$fractionbits = 52;
				break;

			case 80:
				// 80-bit Apple SANE format
				// http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
				$exponentstring = substr($bitword, 1, 15);
				$isnormalized = intval($bitword{16});
				$fractionstring = substr($bitword, 17, 63);
				$exponent = pow(2, getid3_lib::Bin2Dec($exponentstring) - 16383);
				$fraction = $isnormalized + getid3_lib::DecimalBinary2Float($fractionstring);
				$floatvalue = $exponent * $fraction;
				if ($signbit == '1') {
					$floatvalue *= -1;
				}
				return $floatvalue;
				break;

			default:
				return false;
				break;
		}
		$exponentstring = substr($bitword, 1, $exponentbits);
		$fractionstring = substr($bitword, $exponentbits + 1, $fractionbits);
		$exponent = getid3_lib::Bin2Dec($exponentstring);
		$fraction = getid3_lib::Bin2Dec($fractionstring);

		if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
			// Not a Number
			$floatvalue = false;
		} elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
			if ($signbit == '1') {
				$floatvalue = '-infinity';
			} else {
				$floatvalue = '+infinity';
			}
		} elseif (($exponent == 0) && ($fraction == 0)) {
			if ($signbit == '1') {
				$floatvalue = -0;
			} else {
				$floatvalue = 0;
			}
			$floatvalue = ($signbit ? 0 : -0);
		} elseif (($exponent == 0) && ($fraction != 0)) {
			// These are 'unnormalized' values
			$floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * getid3_lib::DecimalBinary2Float($fractionstring);
			if ($signbit == '1') {
				$floatvalue *= -1;
			}
		} elseif ($exponent != 0) {
			$floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + getid3_lib::DecimalBinary2Float($fractionstring));
			if ($signbit == '1') {
				$floatvalue *= -1;
			}
		}
		return (float) $floatvalue;
	}

	function BigEndian2Bin($byteword) {
		$binvalue = '';
		$bytewordlen = strlen($byteword);
		for ($i = 0; $i < $bytewordlen; $i++) {
			$binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT);
		}
		return $binvalue;
	}

	function Bin2Dec($binstring, $signed=false) {
		$signmult = 1;
		if ($signed) {
			if ($binstring{0} == '1') {
				$signmult = -1;
			}
			$binstring = substr($binstring, 1);
		}
		$decvalue = 0;
		for ($i = 0; $i < strlen($binstring); $i++) {
			$decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
		}
		return getid3_lib::CastAsInt($decvalue * $signmult);
	}

	function DecimalBinary2Float($binarynumerator) {
		$numerator   = getid3_lib::Bin2Dec($binarynumerator);
		$denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator)));
		return ($numerator / $denominator);
	}

}








/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or http://www.getid3.org                         //
/////////////////////////////////////////////////////////////////
//                                                             //
// Please see readme.txt for more information                  //
//                                                            ///
/////////////////////////////////////////////////////////////////

// Defines
define('GETID3_VERSION', '1.7.9-20090308');
define('GETID3_FREAD_BUFFER_SIZE', 16384); // read buffer size in bytes



class getID3
{
	// public: Settings
	var $encoding        = 'ISO-8859-1';   // CASE SENSITIVE! - i.e. (must be supported by iconv())
	                                       // Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE

	var $encoding_id3v1  = 'ISO-8859-1';   // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN'

	var $tempdir         = '*';            // default '*' should use system temp dir

	// public: Optional tag checks - disable for speed.
	var $option_tag_id3v1         = true;  // Read and process ID3v1 tags
	var $option_tag_id3v2         = true;  // Read and process ID3v2 tags
	var $option_tag_lyrics3       = true;  // Read and process Lyrics3 tags
	var $option_tag_apetag        = true;  // Read and process APE tags
	var $option_tags_process      = true;  // Copy tags to root key 'tags' and encode to $this->encoding
	var $option_tags_html         = true;  // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities

	// public: Optional tag/comment calucations
	var $option_extra_info        = true;  // Calculate additional info such as bitrate, channelmode etc

	// public: Optional calculations
	var $option_md5_data          = false; // Get MD5 sum of data part - slow
	var $option_md5_data_source   = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
	var $option_sha1_data         = false; // Get SHA1 sum of data part - slow
	var $option_max_2gb_check     = true;  // Check whether file is larger than 2 Gb and thus not supported by PHP

	// private
	var $filename;


	// public: constructor
	function getID3()
	{

		$this->startup_error   = '';
		$this->startup_warning = '';

		// Check for PHP version >= 4.2.0
		if (phpversion() < '4.2.0') {
		    $this->startup_error .= 'getID3() requires PHP v4.2.0 or higher - you are running v'.phpversion();
		}

		// Check memory
		$memory_limit = ini_get('memory_limit');
		if (preg_match('/(\d+)M/i', $memory_limit, $matches)) {
			// could be stored as "16M" rather than 16777216 for example
			$memory_limit = $matches[1] * 1048576;
		}
		if ($memory_limit <= 0) {
			// memory limits probably disabled
		} elseif ($memory_limit <= 3145728) {
	    	$this->startup_error .= 'PHP has less than 3MB available memory and will very likely run out. Increase memory_limit in php.ini';
		} elseif ($memory_limit <= 12582912) {
	    	$this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini';
		}

		// Check safe_mode off
		if ((bool) ini_get('safe_mode')) {
		    $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
		}


		// define a constant rather than looking up every time it is needed
		if (!defined('GETID3_OS_ISWINDOWS')) {
			if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
				define('GETID3_OS_ISWINDOWS', true);
			} else {
				define('GETID3_OS_ISWINDOWS', false);
			}
		}

		// Get base path of getID3() - ONCE
		if (!defined('GETID3_INCLUDEPATH')) {
			foreach (get_included_files() as $key => $val) {
				if (basename($val) == '_flv.php') {
					define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR);
					break;
				}
			}
		}

		// Load support library
//		if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
//			$this->startup_error .= 'getid3.lib.php is missing or corrupt';
//		}


		// Needed for Windows only:
		// Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
		//   as well as other helper functions such as head, tail, md5sum, etc
		// IMPORTANT: This path cannot have spaces in it. If neccesary, use the 8dot3 equivalent
		//   ie for "C:/Program Files/Apache/" put "C:/PROGRA~1/APACHE/"
		// IMPORTANT: This path must include the trailing slash
		if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {

			$helperappsdir = GETID3_INCLUDEPATH/*.'..'.DIRECTORY_SEPARATOR.'helperapps'*/; // must not have any space in this path // !!! patched by Sb

			if (!is_dir($helperappsdir)) {
				$this->startup_error .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist';
			} elseif (strpos(realpath($helperappsdir), ' ') !== false) {
				$DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
				foreach ($DirPieces as $key => $value) {
					if ((strpos($value, '.') !== false) && (strpos($value, ' ') === false)) {
						if (strpos($value, '.') > 8) {
							$value = substr($value, 0, 6).'~1';
						}
					} elseif ((strpos($value, ' ') !== false) || strlen($value) > 8) {
						$value = substr($value, 0, 6).'~1';
					}
					$DirPieces[$key] = strtoupper($value);
				}
				$this->startup_error .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary (on this server that would be something like "'.implode(DIRECTORY_SEPARATOR, $DirPieces).'" - NOTE: this may or may not be the actual 8.3 equivalent of "'.$helperappsdir.'", please double-check). You can run "dir /x" from the commandline to see the correct 8.3-style names.';
			}
			define('GETID3_HELPERAPPSDIR', realpath($helperappsdir).DIRECTORY_SEPARATOR);
		}

	}



	function analyze($filename) {

		if (!empty($this->startup_error)) {
			return $this->error($this->startup_error);
		}
		if (!empty($this->startup_warning)) {
			$this->warning($this->startup_warning);
		}

		// init result array and set parameters
		$this->info = array();
		$this->info['GETID3_VERSION'] = GETID3_VERSION;


		// Disable magic_quotes_runtime, if neccesary
		$old_magic_quotes_runtime = get_magic_quotes_runtime(); // store current setting of magic_quotes_runtime
		if ($old_magic_quotes_runtime) {
			set_magic_quotes_runtime(0);                        // turn off magic_quotes_runtime
			if (get_magic_quotes_runtime()) {
				return $this->error('Could not disable magic_quotes_runtime - getID3() cannot work properly with this setting enabled');
			}
		}

		// remote files not supported
		if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
			return $this->error('Remote files are not supported in this version of getID3() - please copy the file locally first');
		}

		$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
		$filename = preg_replace('#'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#', DIRECTORY_SEPARATOR, $filename);

		// open local file
		if (file_exists($filename) && ($fp = @fopen($filename, 'rb'))) {
			// great
		} else {
			return $this->error('Could not open file "'.$filename.'"');
		}

		// set parameters
		$this->info['filesize'] = filesize($filename);


		// set more parameters
		$this->info['avdataoffset']        = 0;
		$this->info['avdataend']           = $this->info['filesize'];
		$this->info['fileformat']          = '';                // filled in later
		$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
		$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
		$this->info['tags']                = array();           // filled in later, unset if not used
		$this->info['error']               = array();           // filled in later, unset if not used
		$this->info['warning']             = array();           // filled in later, unset if not used
		$this->info['comments']            = array();           // filled in later, unset if not used
		$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired

		// set redundant parameters - might be needed in some include file
		$this->info['filename']            = basename($filename);
		$this->info['filepath']            = str_replace('\\', '/', realpath(dirname($filename)));
		$this->info['filenamepath']        = $this->info['filepath'].'/'.$this->info['filename'];


		// handle ID3v2 tag - done first - already at beginning of file
		// ID3v2 detection (even if not parsing) is always done otherwise fileformat is much harder to detect

			fseek($fp, 0, SEEK_SET);
			$header = fread($fp, 10);
			if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {
				$this->info['id3v2']['header']           = true;
				$this->info['id3v2']['majorversion']     = ord($header{3});
				$this->info['id3v2']['minorversion']     = ord($header{4});
				$this->info['id3v2']['headerlength']     = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length

				$this->info['id3v2']['tag_offset_start'] = 0;
				$this->info['id3v2']['tag_offset_end']   = $this->info['id3v2']['tag_offset_start'] + $this->info['id3v2']['headerlength'];
				$this->info['avdataoffset']              = $this->info['id3v2']['tag_offset_end'];
			}


		// read 32 kb file data
		fseek($fp, $this->info['avdataoffset'], SEEK_SET);
		$formattest = fread($fp, 32774);

		// determine format
		$determined_format = $this->GetFileFormat($formattest, $filename);

		// unable to determine file format
		if (!$determined_format) {
			fclose($fp);
			return $this->error('unable to determine file format');
		}


		// set mime type
		$this->info['mime_type'] = $determined_format['mime_type'];


		// include module
//		include_once(GETID3_INCLUDEPATH.$determined_format['include']);

		// instantiate module class
		$class_name = 'getid3_'.$determined_format['module'];
		if (!class_exists($class_name)) {
			return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
		}
		if (isset($determined_format['option'])) {
			$class = new $class_name($fp, $this->info, $determined_format['option']);
		} else {
			$class = new $class_name($fp, $this->info);
		}
		unset($class);

		// close file
		fclose($fp);


		// remove undesired keys
		$this->CleanUp();

		// restore magic_quotes_runtime setting
		set_magic_quotes_runtime($old_magic_quotes_runtime);

		// return info array
		return $this->info;
	}





	// return array containing information about all supported formats
	function GetFileFormatArray() {
		static $format_info = array();
		if (empty($format_info)) {
			$format_info = array(


				// FLV  - audio/video - FLash Video
				'flv' => array(
							'pattern'   => '^FLV\x01',
							'group'     => 'audio-video',
							'module'    => 'flv',
							'mime_type' => 'video/x-flv',
						),

			);
		}

		return $format_info;
	}



	function GetFileFormat(&$filedata, $filename='') {
		// this function will determine the format of a file based on usually
		// the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
		// and in the case of ISO CD image, 6 bytes offset 32kb from the start
		// of the file).

		// Identify file format - loop through $format_info and detect with reg expr
		foreach ($this->GetFileFormatArray() as $format_name => $info) {
			// Using preg_match() instead of ereg() - much faster
			// The /s switch on preg_match() forces preg_match() NOT to treat
			// newline (0x0A) characters as special chars but do a binary match
			if (preg_match('/'.$info['pattern'].'/s', $filedata)) {
				$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
				return $info;
			}
		}


		if (preg_match('/\.mp[123a]$/i', $filename)) {
			// Too many mp3 encoders on the market put gabage in front of mpeg files
			// use assume format on these if format detection failed
			$GetFileFormatArray = $this->GetFileFormatArray();
			$info = $GetFileFormatArray['mp3'];
			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
			return $info;
		}

		return false;
	}

	function CleanUp() {

		// remove possible empty keys
		$AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
		foreach ($AVpossibleEmptyKeys as $dummy => $key) {
			if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
				unset($this->info['audio'][$key]);
			}
			if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
				unset($this->info['video'][$key]);
			}
		}

		// remove empty root keys
		if (!empty($this->info)) {
			foreach ($this->info as $key => $value) {
				if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
					unset($this->info[$key]);
				}
			}
		}

		// remove meaningless entries from unknown-format files
		if (empty($this->info['fileformat'])) {
			if (isset($this->info['avdataoffset'])) {
				unset($this->info['avdataoffset']);
			}
			if (isset($this->info['avdataend'])) {
				unset($this->info['avdataend']);
			}
		}
	}

	function error($message) {

		$this->CleanUp();

		$this->info['error'][] = $message;
		return $this->info;
	}

}

?>